#include "widget.h"
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QTimer>
#include <QRandomGenerator>
#include <GLES3/gl3.h>

#include "Timer.h"
#include <iostream>
#include <iomanip>

#define IMAGE_WIDTH 500
#define IMAGE_HEIGHT 500

Timer timer, t1, t2;
float copyTime, updateTime;

static const char *vertexShaderSource =
//    "#version 330\n"
      "precision highp float;\n"
        "attribute highp vec4 vertex;\n"
        "attribute mediump vec2 texCoord;\n"
        "varying mediump vec2 v_TexCoord;\n"
        "void main(void)\n"
        "{\n"
        "   gl_Position = vertex;\n"
        "   v_TexCoord = texCoord;\n"
        "}\n";

static const char *fragmentShaderSource =
//    "#version 330\n"
    "precision highp float;\n"
    "varying mediump vec2 v_TexCoord;\n"
    "uniform sampler2D texture;\n"
    "void main() {\n"
    "   gl_FragColor = texture2D(texture, v_TexCoord);\n"
    "}\n";


void updatePixels(unsigned char* dst)
{
#if 0
	for (int i = 0; i < IMAGE_WIDTH * IMAGE_HEIGHT * 4; ++i) {
            dst[i] = QRandomGenerator::global()->bounded(256);
        }
#else
	static int color = 0;

	int* ptr = (int*)dst;

	// copy 4 bytes at once
	for(int i = 0; i < IMAGE_HEIGHT; ++i)
	{
		for(int j = 0; j < IMAGE_WIDTH; ++j)
		{
			*ptr = color;
			++ptr;
		}
		color += 257;
	}
	++color;            // scroll down
#endif
}

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent),
      vbo(QOpenGLBuffer::VertexBuffer)
{
    setWindowFlags(Qt::FramelessWindowHint);
    connect(&timer, &QTimer::timeout, this, &Widget::updateTexture);
    timer.start(5); // 每秒更新一次纹理
}

Widget::~Widget()
{
    makeCurrent();
    glDeleteTextures(1, &texture);
    glDeleteBuffers(2, pboIds);
    doneCurrent();
}

void Widget::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0, 0, 0, 1);

    initShaders();
    initTextures();

    static const float vertices[] = {
        -1.0f, -1.0f, 0.0f, 0.0f,
         1.0f, -1.0f, 1.0f, 0.0f,
        -1.0f,  1.0f, 0.0f, 1.0f,
         1.0f,  1.0f, 1.0f, 1.0f,
    };

    vbo.create();
    vbo.bind();
    vbo.allocate(vertices, sizeof(vertices));

    glGenBuffers(2, pboIds);
    for (int i = 0; i < 2; ++i) {
        glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[i]);
        glBufferData(GL_PIXEL_UNPACK_BUFFER, IMAGE_WIDTH * IMAGE_HEIGHT * 4, nullptr, GL_STREAM_DRAW);
    }
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}

QSize Widget::sizeHint() const
{
	return QSize(1920, 1200);
}

void Widget::resizeGL(int w, int h)
{
    glViewport(0, 0, w, h);
}

void Widget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT);

    program.bind();

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    program.setUniformValue("texture", 0);

    vbo.bind();
    int vertexLocation = program.attributeLocation("vertex");
    program.enableAttributeArray(vertexLocation);
    program.setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 2, 4 * sizeof(float));

    int texcoordLocation = program.attributeLocation("texCoord");
    program.enableAttributeArray(texcoordLocation);
    program.setAttributeBuffer(texcoordLocation, GL_FLOAT, 2 * sizeof(float), 2, 4 * sizeof(float));

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    vbo.release();
    program.release();
}

void Widget::initShaders()
{
    program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
    program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
    program.link();
}

void Widget::initTextures()
{
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // 初始化纹理内容
    unsigned char data[IMAGE_WIDTH * IMAGE_HEIGHT * 4] = { 0 };
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, IMAGE_WIDTH, IMAGE_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}

void Widget::updateTexture()
{
    static int index = 0;
    int nextIndex = (index + 1) % 2;

    t1.start();
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[nextIndex]);
    unsigned char* ptr = (unsigned char*)glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, IMAGE_WIDTH * IMAGE_HEIGHT * 4, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
    if (ptr) {
        // 更新 PBO 数据
        updatePixels(ptr);
        glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
    }
    t1.stop();
    updateTime = t1.getElapsedTimeInMilliSec();


    t1.start();
    glBindTexture(GL_TEXTURE_2D, texture);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pboIds[index]);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    t1.stop();
    copyTime = t1.getElapsedTimeInMilliSec();

    std::cout << std::fixed << std::setprecision(3) << "Updating Time: " << updateTime << " ms" << std::endl;
    std::cout << "Copying Time: " << copyTime << " ms" << std::endl;
    std::cout << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield);

    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

    index = nextIndex;

    update();
}
